//
//  NSOperationViewController.m
//  多线程
//
//  Created by hfzhangzhang on 2018/4/9.
//  Copyright © 2018年 hfzhangzhang. All rights reserved.
//

#import "NSOperationViewController.h"

@interface NSOperationViewController ()
/** 全局操作队列,调用所有的异步操作  */
@property(nonatomic,strong)NSOperationQueue * opQueue;
@end

@implementation NSOperationViewController

/*
 
 NSOperation 是苹果公司大力推荐的"并发"技术! 开发者已经不需要关心线程以及线程的生命周期!
 ,而且我们逃离了GCD的 并发还是串行 异步还是同步!
 
 NSOperation 的核心概念: 将"操作" 添加到 "队列"
 GCD的核心概念: 将"任务"添加到队列,指定任务的执行函数(方法)
 
 NSOperation的作用
 配合使用NSOperation和NSOperationQueue也能实现多线程编程
 
 NSOperation和NSOperationQueue实现多线程的具体步骤
 先将需要执行的操作封装到一个NSOperation对象中
 然后将NSOperation对象添加到NSOperationQueue中
 系统会自动将NSOperationQueue中的NSOperation取出来
 将取出的NSOperation封装的操作放到一条新线程中执行
 
 NSOperation是个抽象类，并不具备封装操作的能力，必须使用它的子类
 使用NSOperation子类的方式有3种
 NSInvocationOperation
 NSBlockOperation
 自定义子类继承NSOperation，实现内部相应的方法

 
 创建NSInvocationOperation对象
 - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
 调用start方法开始执行操作
 - (void)start;
 一旦执行操作，就会调用target的sel方法
 
 注意
 默认情况下，调用了start方法后并不会开一条新线程去执行操作，而是在当前线程同步执行操作
 只有将NSOperation放到一个NSOperationQueue中，才会异步执行操作

 
 创建NSBlockOperation对象
 + (id)blockOperationWithBlock:(void (^)(void))block;
 
 通过addExecutionBlock:方法添加更多的操作
 NSBlockOperation该操作有个方法能在该操作中持续添加操作任务addExecutionBlock，直到全部的block中的任务都执行完成后，该操作op才算执行完毕。当该操作在addExecutionBlock加入比较多的任务时，该op的block中的（包括blockOperationWithBlock和addExecutionBlock中的操作）会在新开的线程中执行。不一定在创建该op的线程中执行。

 - (void)addExecutionBlock:(void (^)(void))block;
 
 注意：只要NSBlockOperation封装的操作数 > 1，就会异步执行操作

 
 
 NSOperationQueue的作用
 NSOperation可以调用start方法来执行任务，但默认是同步执行的
 如果将NSOperation添加到NSOperationQueue（操作队列）中，系统会自动异步执行NSOperation中的操作
 
 添加操作到NSOperationQueue中
 - (void)addOperation:(NSOperation *)op;
 - (void)addOperationWithBlock:(void (^)(void))block;

 
 最大并发数的相关方法
 - (NSInteger)maxConcurrentOperationCount;
 - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

 
 取消队列的所有操作
 - (void)cancelAllOperations;
 提示：也可以调用NSOperation的- (void)cancel方法取消单个操作
 
 暂停和恢复队列
 - (void)setSuspended:(BOOL)b; // YES代表暂停队列，NO代表恢复队列
 - (BOOL)isSuspended;
 
 
 取消某个操作，可以直接调用操作的取消方法cancel。
 //取消整个操作队列的所有操作，这个方法好像没有效果？？？。在主队列中，没有用，如果将操作加入到自定义队列的话，在操作没有开始执行的时候，是能够取消操作的。

 
 NSOperation之间可以设置依赖来保证执行顺序
 比如一定要让操作A执行完后，才能执行操作B，可以这么写
 [operationB addDependency:operationA]; // 操作B依赖于操作A
 
 可以在不同queue的NSOperation之间创建依赖关系
 
 注意：不能相互依赖 比如A依赖B，B依赖A
 
 可以监听一个操作的执行完毕
 - (void (^)(void))completionBlock;
 - (void)setCompletionBlock:(void (^)(void))block;

 
 
 自定义NSOperation的步骤很简单
 重写- (void)main方法，在里面实现想执行的任务
 
 重写- (void)main方法的注意点
 自己创建自动释放池（因为如果是异步操作，无法访问主线程的自动释放池）
 经常通过- (BOOL)isCancelled方法检测操作是否被取消，对取消做出响应

 
 ----------------------------------------------------------------------------------
 GCD & NSOperation 的对比
 GCD 在 iOS 4.0推出的,主要针对多核处理器做了优化的并发技术,是 C 语言!
 - 将"任务"[block]"添加到"队列[串行/并发/主队列/全局],并且指定执行任务的函数[同步/异步]
 - 线程间的通讯 dispatch_get_main_queue()
 - 提供了一些 NSOperation 不具备的功能
 - 一次执行(单例设计模式最常用)
 - 延迟执行
 - 调度组
 - 等等...
 
 NSOperation 在 iOS 2.0 推出的!
 - 将操作[异步执行的任务] 添加到 队列[并发队列],就会立刻异步执行
 - mainQueue
 - 提供了一些 GCD 实现起来比较困难的功能
 - 最大并发线程数
 - 队列的暂停/继续
 - 取消所有操作
 - 指定操作之间的依赖关系(GCD 同步实现)
 ----------------------------------------------------------------------------------
 

 */

-(NSOperationQueue *)opQueue
{
    if (!_opQueue) {
        _opQueue = [[NSOperationQueue alloc]init];
    }
    return _opQueue;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self demo6];
    
}
// MARK : NSInvocationOperation 演示
/**
 结果: 开启多个线程,不会顺序执行 -> GCD 并发队列,异步执行任务
 NSOperation 本质上是对 GCD 的面向对象的封装
 - 队列: 本质上就是 GCD 的并发队列
 - 操作: 异步执行的任务
 */
-(void)demo1{
    NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:@"Hank"];
    //start 方法,会在当前线程执行调度方法
    //[op start];
    //1.队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
    //2.将操作添加到队列 - 会自动异步执行调度方法
    [q addOperation:op];
    
}
-(void)demo2{
    //1.队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
    for (int i = 0 ; i < 10; i ++) {
        NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
        //添加到队列
        [q addOperation:op];
    }
}

-(void)downloadImage:(NSObject * )objc{
    NSLog(@"%@----%@",[NSThread currentThread],objc);
}

//MARK: NSBlockOperation 演示
//NSBlockOperation 所有的代码都写在一起.更加好维护
-(void)demo3{
    //1.队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
    //2.操作
    for (int i = 0; i < 10; i++) {
        NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@ ---- %d",[NSThread currentThread],i);
        }];
        [q addOperation:op];
    }
}
//MARK: 更简单的
-(void)demo4{
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
    //2.操作
    for (int i = 0; i < 10; i++) {
        [q addOperationWithBlock:^{
            NSLog(@"%@ ---- %d",[NSThread currentThread],i);
        }];
    }
    
}
//MARK: 队列  -> 队列如果每次分配会比较浪费!!在实际开发中,会使用全局的队列,统一调度所有异步执行的操作 - 只要是 NSOperation 的子类,都可以添加到队列!
-(void)demo5{
    //直接添加任务
    [self.opQueue addOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%@---%d",[NSThread currentThread],i);
        }
    }];
    
    //block operation
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"BLOCK ---- %@",[NSThread currentThread]);
    }];
    [self.opQueue addOperation:op1];
    
    //invocationOperarion
    NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:@"hank"];
    [self.opQueue addOperation:op2];
    
}

// MARK : 线程间通信
-(void)demo6{
    [self.opQueue addOperationWithBlock:^{
        NSLog(@"耗时操作!! %@",[NSThread currentThread]);
        //主线程 更新UI ,主队列
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"UIUIUI --- %@",[NSThread currentThread]);
        }];
    }];
}



//MARK : 最大并发数
-(void)demo7{
    //设置同时最大的并发操作数量
    //WIFI: 5 至 6
    //流量 : 2 到 3
    self.opQueue.maxConcurrentOperationCount = 2;
    //添加操作进队列
    /*
     从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
     在 iOS 7.0 以前,GCD 通常只会开启 5  6条线程!
     目前线程多了说明:
     1.底层的现场池更大了,能够拿到的线程资源多了!
     2.多控制同时并发的现场数,要求就更高了!
     */
    
    for (int i = 0;i < 20; i++) {
        [self.opQueue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@---%d",[NSThread currentThread],i);
        }];
    }
    
    
}

//MARK: 依赖关系
-(void)dependecy{
    /*
     例子: 下载\解压\通知用户
     */
    //1.下载
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:.5];
    }];
    //2.解压
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"解压---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    //3.通知用户
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用户---%@",[NSThread currentThread]);
    }];
    //NSOperation 提供了依赖关系
    //!!!! 注意,不要指定循环依赖,队列就不工作了!!
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    //添加到队列中 waitUntilFinished:是否等待! 会卡住当前线程!!
    [self.opQueue addOperations:@[op1,op2] waitUntilFinished:NO];
    //主线程通知用户
    [[NSOperationQueue mainQueue] addOperation:op3];
    
    NSLog(@"come here %@",[NSThread currentThread]);
}



//MARK : 取消所有操作
/*
 1.队列挂起的时候,不会清空内部的操作.只有在队列继续的时候才会清空!
 2.正在执行的操作也不会被取消!
 */
-(void)cancelAll{
    NSLog(@"取消所有操作");
    //取消操作
    [self.opQueue cancelAllOperations];
    NSLog(@"取消之后的操作数 :%tu",self.opQueue.operationCount);
    
}

//MARK : 暂停&继续
/*
 当挂起队列的时候,正在执行的操作不受影响!
 suspended      : 决定队列的暂停和继续
 operationCount : 队列中的操作数
 */
-(void)pause{
    //判断我们队列是否挂起
    if(self.opQueue.isSuspended){
        NSLog(@"继续 %tu",self.opQueue.operationCount);
        self.opQueue.suspended = NO;
    }else{
        NSLog(@"暂停 %tu",self.opQueue.operationCount);
        self.opQueue.suspended = YES;
    }
}





@end
